<template>
  <div class="drag-lists" ref="drag-lists">
    <slot />
    <!-- 占位dom -->
    <div class="perch" ref="perchRef"></div>
  </div>
</template>
 
<script>
export default {
  props: {
    direction: {
      default: 'y' // 可选值x：横向, y：纵向
    }
  },
  data() {
    return {
      isDrag: false, // 是否拖拽
      index: '', // 拖拽的列表index
      perchEl: null, // 占位div的dom
      parentEl: null, // 列表父dom
      listsEl: null, // 列表dom集合
      pointerPosition: {
        left: '',
        top: ''
      }, // 鼠标点击的点相对于当前dom的左和上偏移量
      dragListOldStyle: '', // list的原始style
      // 被拖拽div的宽高
      dragListElBaseProp: {
        width: '',
        height: ''
      }
    }
  },
  computed: {
    // 被拖拽dom
    dragListEl() {
      return this.listsEl[this.index]
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      document.addEventListener('mousedown', this.mouseDown)
      document.addEventListener('mousemove', this.mouseMove)
      document.addEventListener('mouseup', this.mouseUp)
      this.parentEl = this.$refs['drag-lists']
      this.listsEl = this.$slots.default.map((i) => i.elm) // 获取到slot插槽列表dom集合
      this.perchEl = this.$refs['perchRef']
      // 每个list加mouseenter事件（排序的逻辑）
      this.listsEl.forEach((el) => {
        el.addEventListener('mouseenter', () => {
          if (this.isDrag) {
            const lists = [...this.parentEl.children]
            const imgIndex = lists.findIndex((item) => el === item)
            const perchIndex = lists.findIndex((item) => this.perchEl === item)
            if (perchIndex < imgIndex) {
              this.parentEl.insertBefore(el, this.perchEl)
            } else {
              this.parentEl.insertBefore(this.perchEl, el)
            }
          }
        })
      })
    },
    mouseDown(e) {
      this.isDrag = true
      const { pageX: X, pageY: Y } = e
      this.index = e.target.dataset.index
      if (this.index && this.isDrag) {
        this.getDragListBaseProp()
        this.dragListOldStyle = this.dragListEl.getAttribute('style') || '' // 记录下被拖拽dom的style样式，方便mouseup时恢复
 
        // 设置占位dom的影响宽高的定位的样式和被拖拽dom的保持一致
        const { width, height, offsetLeft, offsetTop, margin, padding } = this.dragListElBaseProp
        this.perchEl.style.width = width + 'px'
        this.perchEl.style.height = height + 'px'
        this.perchEl.style.margin = margin
        this.perchEl.style.padding = padding
        this.perchEl.style.display = 'block' // 占位div显示
 
        this.parentEl.insertBefore(this.perchEl, this.dragListEl) // 在拖拽div前插入占位div
 
        this.pointerPosition = {
          left: X - offsetLeft,
          top: Y - offsetTop
        }
        this.move(this.dragListEl, X - this.pointerPosition.left, Y - this.pointerPosition.top) // 移动被拖拽的div
      }
    },
    mouseMove(e) {
      const { pageX: X, pageY: Y } = e
      if (this.index && this.isDrag) {
        this.move(this.dragListEl, X - this.pointerPosition.left, Y - this.pointerPosition.top) // 移动被拖拽的div
      }
    },
    mouseUp() {
      this.isDrag = false
      this.perchEl.style.display = 'none'
      if (this.dragListEl) {
        if (this.dragListOldStyle) {
          this.dragListEl.setAttribute('style', this.dragListOldStyle)
        } else {
          this.dragListEl.removeAttribute('style')
        }
        this.parentEl.insertBefore(this.dragListEl, this.perchEl)
      }
    },
    // 移动函数
    move(el, x, y) {
      // 如果移动模式是横向，则垂直方向偏移量固定为0
      if (this.direction === 'x') {
        y = 0
      }
      // 如果移动模式是纵向，则水平方向偏移量固定为0
      if (this.direction === 'y') {
        x = 0
      }
      // 设置宽高是position为absolute时，防止flex等布局下宽高获取不到的问题
      const { width, height } = this.dragListElBaseProp
      el.setAttribute(
        'style',
        `position: absolute;
          width: ${width}px;
          height: ${height}px;
          left: ${x}px;
          top: ${y}px;
          pointer-events: none;
          border-radius: 5px;
          background: #999;
          opacity: 0.7;
          ${this.dragListOldStyle}`
      )
    },
    // 获取被拖拽dom在拖拽前的宽高，左上偏移量，margin，padding
    getDragListBaseProp() {
      const dom = this.dragListEl
      const margin = dom.currentStyle ? dom.currentStyle['margin'] : getComputedStyle(dom, null)['margin']
      const padding = dom.currentStyle ? dom.currentStyle['padding'] : getComputedStyle(dom, null)['padding']
      const { width, height } = dom.getBoundingClientRect()
      const { offsetLeft, offsetTop } = dom
      this.dragListElBaseProp = { width, height, offsetLeft, offsetTop, margin, padding }
    }
  }
}
</script>
<style lang="scss" scoped>
.drag-lists {
  position: relative;
  .perch {
    display: none;
  }
}
</style>